/*
 * html.c - cve-check-tool helpers
 *
 * Copyright (C) 2015 Intel Corporation
 *
 * cve-check-tool is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

#define _GNU_SOURCE

#include <errno.h>
#include <stdio.h>

#include "config.h"
#include "cve-check-tool.h"
#include "cve-string.h"
#include "plugin.h"
#include "template.h"
#include "util.h"

#define TMPL(X) DATA_DIRECTORY G_DIR_SEPARATOR_S X ".template"

#define TMPL_TOTAL TMPL("packages")

#define NVD_CVE_URI "http://web.nvd.nist.gov/view/vuln/detail?vulnId="

static inline bool load_template(const char *tmpl_name, gchar **ret)
{
        autofree(GError) *error = NULL;

        if (!g_file_get_contents(tmpl_name, ret, NULL, &error)) {
                g_printerr("Unable to access mandatory template: %s\n", tmpl_name);
                *ret = NULL;
                return false;
        }
        return true;
}

static inline gchar *vector_map(gchar *vector)
{
        if (!vector) {
                return "";
        }
        if (g_str_equal(vector, ACCESS_VECTOR_ADJACENT)) {
                return "Adjacent Network";
        } else if (g_str_equal(vector, ACCESS_VECTOR_LOCAL)) {
                return "Local";
        } else if (g_str_equal(vector, ACCESS_VECTOR_NETWORK)) {
                return "Remote (Network)";
        } else {
                return vector;
        }
}

/**
 * Textual representation of bug status
 */
static inline gchar *status_map(ReportStatus status)
{
        switch (status) {
        case REPORT_STATUS_OPEN:
                return "Open";
        case REPORT_STATUS_CLOSED:
                return "Closed";
        case REPORT_STATUS_CLOSED_WILLNOTFIX:
                return "Closed-Will Not Fix";
        default:
                return "Unreported";
        }
}

#define LOAD_TEMPLATE(name, ret)                                                                                       \
        if (!load_template(name, ret)) {                                                                               \
                return false;                                                                                          \
        }

static bool write_report(CveCheckTool *self)
{
        autofree(gchar) *aff = NULL;
        autofree(gchar) *body = NULL;
        autofree(cve_string) *report = NULL;
        GHashTableIter iter;
        gchar *key = NULL;
        struct source_package_t *v = NULL;
        GList *c = NULL;
        struct cve_entry_t *c_entry = NULL;
        gint affected = 0;
        guint row = 0;
        autofree(TemplateContext) *context = NULL;
        int ret;

        /* Mandatory template files */
        LOAD_TEMPLATE(TMPL_TOTAL, &body);
        context = template_context_new();
        template_context_add_bool(context, "bugs_enabled", self->bugs);
        template_context_add_string(context, "cve_uri_root", NVD_CVE_URI);

        g_hash_table_iter_init(&iter, self->db);
        while (g_hash_table_iter_next(&iter, (void **)&key, (void **)&v)) {
                bool hit = false;
                if (!v->issues && !v->patched && !self->show_unaffected) {
                        continue;
                }
                if (!v->issues && self->hide_patched) {
                        continue;
                }

                for (c = v->issues; c; c = c->next) {
                        c_entry = cve_db_get_cve(self->cve_db, (gchar *)c->data);
                        if (self->modified > 0 && c_entry->modified > self->modified) {
                                cve_free(c_entry);
                                continue;
                        }
                        hit = true;

                        TemplateContext *child = NULL;
                        child = template_context_new();
                        template_context_add_string(child, "package_name", key);
                        template_context_add_string(child, "package_version", v->version);
                        template_context_add_string(child, "cve", c_entry->id);
                        template_context_add_string(child, "desc", c_entry->summary);
                        template_context_add_string(child, "row_class", row % 2 ? "even" : "odd");
                        template_context_add_string(child, "score", c_entry->score);
                        template_context_add_string(child, "vector", vector_map(c_entry->vector));
                        template_context_add_string(child, "status_class", "not-patched");
                        if (c_entry->status != REPORT_STATUS_CLOSED_WILLNOTFIX) {
                                template_context_add_string(child, "status_string", "Check");
                        } else {
                                template_context_add_string(child, "status_string", "Ignore");
                        }
                        /* TODO: Emit URI */
                        template_context_add_string(child, "report_status", status_map(c_entry->status));
                        template_context_add_list(context, "packages", child);
                        ++row;
                        cve_free(c_entry);
                }
                if (!self->hide_patched && v->patched) {
                        for (c = v->patched; c; c = c->next) {
                                c_entry = cve_db_get_cve(self->cve_db, (gchar *)c->data);

                                if (self->modified > 0 && c_entry->modified > self->modified) {
                                        cve_free(c_entry);
                                        continue;
                                }
                                hit = true;

                                TemplateContext *child = NULL;
                                child = template_context_new();
                                template_context_add_string(child, "package_name", key);
                                template_context_add_string(child, "package_version", v->version);
                                template_context_add_string(child, "cve", c_entry->id);
                                template_context_add_string(child, "desc", c_entry->summary);
                                template_context_add_string(child, "row_class", row % 2 ? "even" : "odd");
                                template_context_add_string(child, "score", c_entry->score);
                                template_context_add_string(child, "vector", vector_map(c_entry->vector));
                                template_context_add_string(child, "status_class", "patched");
                                template_context_add_string(child, "status_string", "Patched");
                                /* TODO: Emit URI */
                                template_context_add_string(child, "report_status", status_map(c_entry->status));
                                template_context_add_list(context, "packages", child);
                                ++row;
                                cve_free(c_entry);
                        }
                }
                if (hit) {
                        ++affected;
                }
        }

        aff = g_strdup_printf("CVE Report for %d package%s", affected, affected > 1 ? "s" : "");

        template_context_add_string(context, "affected_string", aff);
        report = template_context_process_line(context, body, false);

        /* Write report to named file */
        if (self->output_file) {
                if (!cve_file_set_text(self->output_file, report->str)) {
                        fprintf(stderr, "Unable to write report: %s\n", strerror(errno));
                        return false;
                }
                return true;
        }

        /* Write report to stdout */
        ret = printf("%s\n", report->str);
        if (ret < 0) {
                g_printerr("Unable to write report: %s\n", strerror(errno));
                return false;
        }
        if (!ret || ret != cstrlen(report) + 1) {
                g_printerr("Report was truncated\n");
                return false;
        }

        return true;
}

_module_export_ bool cve_plugin_module_init(CvePlugin *self)
{
        self->report = write_report;
        self->flags = PLUGIN_TYPE_REPORT;
        self->name = "html";
        return true;
}

/*
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 8
 * tab-width: 8
 * indent-tabs-mode: nil
 * End:
 *
 * vi: set shiftwidth=8 tabstop=8 expandtab:
 * :indentSize=8:tabSize=8:noTabs=true:
 */
